<?php
/**
 *  ==============================================================
 *  Created by PhpStorm.
 *  User: Ice
 *  邮箱: ice@sbing.vip
 *  网址: https://sbing.vip
 *  Date: 2020/7/9 上午10:30
 *  ==============================================================
 */

namespace addons\error500\library;


use think\exception\ErrorException;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\facade\Log;
use think\facade\View;

class ErrorHandle
{
    /**
     * 捕获异常跳转至500页面
     *
     * @param  \Throwable  $e
     */
    public static function throw500(\Throwable $e)
    {
        if ($e instanceof HttpException) {
            if ($e->getStatusCode() == 404) {
                //TODO: 400模板
                $url = addon_url('error500/index/error404');
                throw new HttpResponseException(redirect($url));
            }
            $url = addon_url('error500/index/error500');
            throw new HttpResponseException(redirect($url));
        } elseif ($e instanceof ErrorException) {
            $url = addon_url('error500/index/error500');
            throw new HttpResponseException(redirect($url));
        }
    }

    /**
     * 发送异常邮件
     *
     * @param  \Throwable  $e
     * @param  array       $ignoreReport
     */
    public static function throwReportEmail(\Throwable $e, array $ignoreReport)
    {
        $isIgnore = false;
        foreach ($ignoreReport as $class) {
            if ($e instanceof $class) {
                $isIgnore = true;
            }
        }
        if ($isIgnore === false) {
            //send email
            $config = get_addon_config('error500');
            if (isset($config['email']) && !empty($config['email'])) {
                $temlpate = self::getTemplate();
                $data     = [
                    'class'   => self::getClass($e),
                    'message' => $e->getMessage(),
                    'code'    => (int) $e->getCode(),
                    'file'    => $e->getFile().':'.$e->getLine(),
                    'trace'   => self::normalizeException($e),
                ];
                $app_name = config('site.name');
                $messsage = View::display($temlpate, $data);
                hook('ems_notice', [
                    'subject' => '['.$app_name.']：异常警告！',
                    'email'   => $config['email'],
                    'msg'     => $messsage,
                ]);
            }

        }
    }

    /**
     * 邮箱模板
     *
     * @return string
     */
    private static function getTemplate()
    {
        $str = 'File:{$file}<br/>Code:{$code}<br/>Message:{$message}<br/>Class:{$class}<br/>';
        $str .= '{foreach $trace as $k=>$t}';
        $str .= '<b>Trace {$k}:</b><br/>';
        $str .= 'File:{$t.file},Line:{$t.line},Code:{$t.code},Message:{$t.message},Class:{$t.class}<br/>';
        $str .= '{$t.trace|raw}<br/>';
        $str .= '<b>Source {$k}:</b><br/>';
        $str .= '{$t.source|raw}<br/>';
        $str .= '{/foreach}';
        return $str;
    }

    /**
     * 格式化异常信息
     *
     * @param  \Throwable  $e
     *
     * @return array
     */
    private static function normalizeException(\Throwable $e)
    {
        $traces        = [];
        $nextException = $e;
        do {
            $traces[] = [
                'class'   => self::getClass($nextException),
                'file'    => $nextException->getFile(),
                'line'    => $nextException->getLine(),
                'code'    => $nextException->getCode(),
                'message' => $nextException->getMessage(),
                'trace'   => self::getTraceString($nextException->getTrace()),
                'source'  => self::getSourceCode($nextException),
            ];
        } while ($nextException = $nextException->getPrevious());
        return $traces;
    }

    /**
     * 追踪错误信息
     *
     * @param $trace
     *
     * @return string
     */
    private static function getTraceString($trace)
    {
        $str = '';
        for ($i = 0, $count = count($trace); $i < $count; ++$i) {
            $class    = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
            $type     = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
            $function = $trace[$i]['function'];
            $file     = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
            $line     = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';

            $str .= sprintf(' %s%s%s() at <b>%s:%s</b><br/>', $class, $type, $function, $file, $line);
        }
        return $str;
    }

    /**
     * 获取类名
     *
     * @param $object
     *
     * @return string
     */
    private static function getClass($object): string
    {
        $class = \get_class($object);

        return 'c' === $class[0] && 0 === strpos($class,
            "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
    }

    /**
     * 获取出错文件内容
     * 获取错误的前9行和后9行
     *
     * @access protected
     *
     * @param  \Throwable  $exception
     *
     * @return string                 错误文件内容
     */
    private static function getSourceCode(\Throwable $exception)
    {
        // 读取前9行和后9行
        $line  = $exception->getLine();
        $first = ($line - 9 > 0) ? $line - 9 : 1;
        $str   = '';
        try {
            $contents = file($exception->getFile()) ?: [];
            $str      .= 'File:'.$exception->getFile().'<br/>'.implode('<br/>',
                    array_slice($contents, $first - 1, 19)).'<br/>';
        } catch (\Exception $e) {
        }

        return $str;
    }
}